/*
 * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2006.
 *
 * Licensed under the Aduna BSD-style license.
 */
 
package edu.tju.commons.io;

import java.io.IOException;
import java.io.InputStream;

/**
 * An InputStream that registers the position of the next character to read.
 * This position can be a character offset or a combination of a line and column
 * number. A line is considered to be terminated by any one of a line feed
 * ('\n'), a carriage return ('\r') , or a carriage return followed immediately
 * by a linefeed ('\r\n').
 * <p>
 * <em>Note: this class is only guaranteed to work correctly when the stream contains US-ASCII characters.</em>
 */
public class CharPosInputStream extends InputStream {

	/** The reader to do the registration on. * */
	private InputStream in = null;

	/** The number of characters read so far. * */
	private int charCount = 1;

	/** The number of lines read so far. * */
	private int lineNo = 1;

	/** The number of characters in the current line read so far. * */
	private int colNo = 1;

	/**
	 * Flag indicating whether the previous character was a carriage return
	 * ('\r').
	 */
	private boolean prevWasCR = false;

	/**
	 * Creates a new CharPosInputStream.
	 */
	public CharPosInputStream(InputStream r) {
		in = r;
	}

	/**
	 * Get the line number (1-based) for the next character to read.
	 */
	public int getLineNo() {
		return lineNo;
	}

	/**
	 * Get the column (1-based) for the next character to read.
	 */
	public int getColumn() {
		return colNo;
	}

	/**
	 * Get the character position (1-based) for the next character to read.
	 */
	public int getCharPos() {
		return charCount;
	}

	@Override
	public int read() throws IOException {
		int c = in.read();

		if (c != -1) {
			charCount++;

			if (c == '\r') {
				lineNo++;
				colNo = 1;
				prevWasCR = true;
			}
			else if (c == '\n') {
				if (!prevWasCR) {
					lineNo++;
					colNo = 1;
				}
				else {
					// Do nothing, lineNo has already been increased
					prevWasCR = false;
				}
			}
			else {
				colNo++;
				prevWasCR = false;
			}
		}

		return c;
	}

	@Override
	public int read(byte[] buf, int off, int len) throws IOException {
		// Return one line at a time. Returning more characters frustrates
		// the character position reporting when used with tools that
		// request large chunks of data at a time.

		int charsRead = 0;

		while (charsRead < len) {
			int c = in.read();

			// Check for EOF:
			if (c == -1) {
				if (charsRead == 0) {
					// No characters read
					return -1;
				}
				else {
					return charsRead;
				}
			}

			// EOF not yet reached
			buf[off + charsRead] = (byte) c;
			charsRead++;

			charCount++;

			if (c == '\r') {
				lineNo++;
				colNo = 1;
				prevWasCR = true;
				break;
			}
			else if (c == '\n') {
				if (!prevWasCR) {
					lineNo++;
					colNo = 1;
					break;
				}
				else {
					// Do nothing, lineNo has already been increased
					prevWasCR = false;
				}
			}
			else {
				colNo++;
				prevWasCR = false;
			}
		}

		return charsRead;
	}

	@Override
	public void close()
		throws IOException {
		in.close();
	}
}
